package newcrunch.grader;

import compiler.ClassFileManager;
import compiler.DynamicJava;
import diff.viewer.DiffPane;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import newcrunch.gui.RightTab;
import newcrunch.util.Logging;
import org.netbeans.swing.tabcontrol.TabData;
import org.netbeans.swing.tabcontrol.TabDataModel;

/**
 *
 * @author ccadm
 */
public class AutoTestManager 
{
    private ClassFileManager classManager;
    private RightTab rightTab;
    private String result;
    public static final String TEST_THREAD = "TestThread";
    
    public AutoTestManager()
    {
    }
    
    private void setResult(String newRes)
    {
        result = newRes;
    }
    
    public void setRightTab(RightTab tab)
    {
        rightTab = tab;
        rightTab.getTestButton().addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                final String filename = rightTab.getFilename();
                if (filename == null)
                    return;
                
                final String code = rightTab.getText();
                
                TabDataModel inputModel = rightTab.getInputTabbedPane().getModel();
                TabDataModel outputModel = rightTab.getOutputTabbedPane().getModel();
                List<String> inputData = getData(inputModel);
                List<String> outputData = getData(outputModel);
                
                int correctCases = 0;
                
                TabDataModel model = rightTab.getAutoTestTabs().getModel();
                
                if (model.size() > 0)
                    model.removeTabs(0, model.size() - 1);
                
                //Compiles the file first.
                try {
                    String output = compile(filename, code);
                    if (!"".equals(output))
                    {
                        rightTab.getCompilerOutput().setText(output);
                        focusCompileTab();
                        return;
                    }
                } catch (IOException exp) {
                    Logging.log(exp);
                    return;
                }
                
                for (int i = 0; i < inputData.size(); i++)
                {                    
                    setResult("");
                    final String curr = inputData.get(i);
                    try {
                        AutoTestManager.this.setResult(AutoTestManager.this.testFile(filename, code, curr));
                    } catch (IOException ex) {
                        Logging.log(ex);
                    } catch (ClassNotFoundException ex) {
                        Logging.log(ex);
                    } catch (NoSuchMethodException ex) {
                        Logging.log(ex);
                    }

                    //change to a common line separator
                    result = result.replaceAll(System.getProperty("line.separator"), "\n");
                    String currData = outputData.get(i).replaceAll(System.getProperty("line.separator"), "\n");
                    if (!result.equals(currData))
                    {
                        DiffPane pane = new DiffPane();
                        pane.getEditorPane1().setText(result);
                        pane.getEditorPane2().setText(outputData.get(i));
                        TabData data = new TabData(pane, null, "Test #" + (i + 1), "Test #" + (i + 1));
                        model.addTab(model.size(), data);
                    }
                    else
                        correctCases++;
                }
                
                //To resolve bug of tabbedcontainer still displaying the previously opened diffeditor.
                if (correctCases == inputData.size())
                {
                    model.addTab(0, new TabData(new javax.swing.JPanel(), null, "", ""));
                    rightTab.getAutoTestTabs().getSelectionModel().setSelectedIndex(0);
                    final TabDataModel mod = model;
                    EventQueue.invokeLater(new Runnable(){
                        public void run()
                        {
                            mod.removeTabs(0, 0);
                        }
                    });
                }
                
                rightTab.getTestCasesDisp().setText(correctCases + "/" + inputData.size() + " correct");
                rightTab.focusAutoTestTab();
            }
        });
    }
    
    private List<String> getData(TabDataModel model)
    {
        List<TabData> tabs = model.getTabs();
        List<String> result = new ArrayList<String>(tabs.size());
        for (TabData currTab : tabs)
        {
            JScrollPane scrollPane = (JScrollPane)currTab.getComponent();
            JTextArea area = (JTextArea)scrollPane.getViewport().getView();
            result.add(area.getText());
        }
        return result;
    }
    
    
    
    private void focusCompileTab()
    {
        rightTab.focusCompileTab();
    }
    
    
    
    /**
     * Assuming that the file has been compiled, runs the given code using the given input.
     * @param filename
     * @param code
     * @param input
     * @return the output from running the code wit 
     */
    private String testFile(String filename, String code, String input) throws IOException, ClassNotFoundException, NoSuchMethodException
    {        
        String className = getClassName(filename);
        List<Class<?>> classes = classManager.getCompiledClasses();
        Class<?> compiledClass = null;
        for (Class<?> clazz : classes)
        {
            if (className.equals(clazz.getName()))
            {
                compiledClass = clazz;
                break;
            }
        }
        
        InputStream oldInputStream = System.in;
        PrintStream oldOutputStream = System.out;
        
        ByteArrayInputStream inStream = new ByteArrayInputStream(input.getBytes());
        System.setIn(inStream);
        
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        final PrintStream currPrintStream = new PrintStream(outStream);
        System.setOut(currPrintStream);
        
        final Method mainMethod = compiledClass.getMethod("main", String[].class);
        final Class<?> compiled = compiledClass;
        final boolean[] ran = new boolean[1];
        try {
            Thread currThread = new Thread(TEST_THREAD) {
                public void run()
                {
                    synchronized (ran) 
                    {
                        try {
                            mainMethod.invoke(compiled, (Object)new String[0]);
                        } catch (IllegalAccessException ex) {
                            Logging.log(ex);
                        } catch (IllegalArgumentException ex) {
                            Logging.log(ex);
                        } 
                        catch (InvocationTargetException ex) {
                            Throwable cause = ex.getCause();
                            StackTraceElement[] stack = filterStackTrace(cause.getStackTrace());
                            cause.setStackTrace(stack);
                            ex.getCause().printStackTrace(currPrintStream);
                        }
                        finally {
                            ran[0] = true;
                            ran.notifyAll();
                        }
                    }
                }
            };
            currThread.start();
            synchronized (ran)
            {
                while (!ran[0])
                    ran.wait();
            }
        }
        catch (IllegalArgumentException ex) {
            Logging.log(ex);
        }
        finally {
            outStream.close();
            System.setIn(oldInputStream);
            System.setOut(oldOutputStream);
            return outStream.toString();
        }
    }
    
    private StackTraceElement[] filterStackTrace(StackTraceElement[] stackTrace)
    {
        List<StackTraceElement> filteredList = new ArrayList<StackTraceElement>();
        for (StackTraceElement element : stackTrace)
        {
            if (element.isNativeMethod())
                break;
            filteredList.add(element);
        }
        StackTraceElement[] output = new StackTraceElement[filteredList.size()];
        return filteredList.toArray(output);
    }
    
    private String getClassName(String filename)
    {
        if (filename.indexOf(".") != -1)
            return filename.substring(0, filename.indexOf("."));
        else
            return filename;
    }
    
    private String compile(String filename, String text) throws IOException
    {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        classManager = DynamicJava.getClassManager(filename, text, bos);
        
        return bos.toString();
    }
}
